<?php
/**
 * Zhub - A PHP Framework For API Developer
 *
 * This file is part of the Zhub package.
 *
 * (c) YiLong Fan <fantiq@163.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @author fanyilong <fantiq@163.com>
 * @copyright 2017 fanyilong. All rights reserved.
 * @package Zhub
 * @date 18-3-7
 */

namespace Zhub\DataBase\Grammar\MySQL;


use Zhub\DataBase\Grammar\Exceptions\MissingTableNameException;
use Zhub\DataBase\Grammar\Grammar;

class Query extends Grammar
{
    const ORDER_ASC  = 0b1;
    const ORDER_DESC = 0b0;

    const JOIN_LEFT   = 01;
    const JOIN_RIGHT  = 02;
    const JOIN_INNER  = 03;
    const JOIN_NORMAL = 04;

    const SELECT   = 00;
    const FROM     = 01;
    const JOIN     = 02;
    const GROUP_BY = 03;
    const HAVING   = 04;
    const WHERE    = 05;
    const ORDER_BY = 06;
    const LIMIT    = 07;

    public function __construct(string $tableName = null)
    {
        $this->initialize();
        parent::__construct($tableName);
        if ($tableName) {
            $this->from($tableName);
        }
    }

    public function select(string ...$fields):self
    {
        $this->sqlComponent[self::SELECT] = empty($fields) ?
            '*' : implode(',', $fields);
        return $this;
    }

    public function from(string $tableName):self
    {
        $this->tableName = $tableName;
        if (!$this->sqlComponent[self::SELECT]) {
            $this->select();
        }
        $this->sqlComponent[self::FROM] = "FROM $this->tableName";
        return $this;
    }

    public function join(int $type = self::JOIN_NORMAL, string $joinTableName, $on):self
    {
        $this->sqlComponent[self::JOIN] = $this->getJoinExpress($type) .
            " $joinTableName ON $on";
        return $this;
    }

    protected function getJoinExpress(int $type = self::JOIN_NORMAL)
    {
        $express = '';
        switch ($type) {
            case self::JOIN_LEFT:
                $express = "LEFT";
                break;
            case self::JOIN_RIGHT:
                $express = "RIGHT";
                break;
            case self::JOIN_INNER:
                $express = "INNER";
                break;
            case self::JOIN_NORMAL:
            default:
        }
        return trim($express . ' JOIN', ' ');
    }

    public function groupBy(string $field):self
    {
        $this->sqlComponent[self::GROUP_BY] = "GROUP BY $field";
        return $this;
    }

    /**
     * @return Query
     * TODO to achieve the group having method
     */
    public function having():self
    {
        return $this;
    }

    public function where(string $where):self
    {
        $this->sqlComponent[self::WHERE] = "WHERE $where";
        return $this;
    }

    public function orderBy(string $field, int $order = self::ORDER_ASC):self
    {
        $this->sqlComponent[self::ORDER_BY] = "ORDER BY $field " . ($order ? 'ASC' : 'DESC');
        return $this;
    }

    public function limit(int $offset, int $limit = null):self
    {
        $this->sqlComponent[self::LIMIT] = "LIMIT $offset" . ($limit ? ",$limit" : '');
        return $this;
    }

    public function toSql():string
    {
        $hasException = false;
        $sql = '';
        foreach ($this->sqlComponent as $k => $component) {
            if (!$component) {
                if ($k == self::FROM) {
                    $hasException = true;
                }
                continue;
            }
            $sql .= $component . ' ';
        }
        if ($hasException) {
            throw new MissingTableNameException($sql);
        }
        return $sql;
    }

    public function initialize()
    {
        parent::initialize();

        $this->sqlComponent = [
            self::SELECT   => null,
            self::FROM     => null,
            self::JOIN     => null,
            self::GROUP_BY => null,
            self::HAVING   => null,
            self::WHERE    => null,
            self::ORDER_BY => null,
            self::LIMIT    => null,
        ];
    }

}